Skip to main content

การ Authentication ใน FastAPI ด้วย Firebase Admin SDK

· 11 min read

generate from Bing


การ Authentication ใน FastAPI ด้วย Firebase Admin SDK

สวัสดีครับ เหล่านักพัฒนาผู้รักความสะดวก!

เบื่อไหมครับกับการเขียนโค้ด Authentication ซ้ำๆ วนไปวนมา ‍ อยากจะมีระบบที่ใช้งานง่าย ปลอดภัย และรวดเร็วทันใจ ⚡️ ถ้าใช่! บทความนี้เหมาะกับคุณมาก

วันนี้ผมจะมาแนะนำวิธีการใช้ Firebase Admin กับ FastAPI สองยักษ์ใหญ่ที่จะช่วยให้คุณสร้างระบบ Authentication สุดเจ๋งได้แบบง่ายๆ โดยไม่ต้องเสียเวลาเขียนโค้ดเยอะแยะ บทความนี้แทบจะละเอียดมาก ไม่เหมาะกับผู้ขี้เกลียดอ่านอะไรเยอะแยะ 😁 เพราะงั้น ขั้นตอนไหนคุณรู้อยู่แล้วข้ามไปได้เลย 🧐

เตรียมตัวให้พร้อม แล้วไปลุยกันเลย!

ขอบเขตเครื่องมือ

ToolVersionLink
Python3.11.5https://www.python.org/downloads/release/python-3115/
Visual Studiocurrenthttps://code.visualstudio.com/
Postmancurrenthttps://www.postman.com/
OSWindows 11

เริ่มต้นใช้งาน Firebase Admin

  1. เข้าใช้งาน Firebase ก่อน

  • ตั้งชื่อได้อิสระได้เลย เสร็จแล้วก็กด Continue โล้ดดดด

  • จะเป็นการถามว่าจะใช้งาน Google Analytics หรือเปล่า ถ้าไม่ก็ติ๊กตรง Enable… มันก็จะปิด แต่ว่าระบบแนะนำให้เปิด ก็เปิดแล้วกัน จากนั้นกด Continue ได้เลย แต่กรณีคุณปิดมันจะเป็นปุ่ม Create project

  • หากคุณเปิดเพราะว่าเชื่อบทความ (^_^) ทำการเลือกบัญชีได้เลย แต่หากไม่เคยใช้ Google Analytics อาจจะต้องสร้างบัญชีก่อนนะครับถึงจะมีให้เลือก แต่ถ้าไม่อยากวุ่นกดที่ Previous เพื่อย้อนกลับไปปิดก็ได้เช่นกัน เพื่อทุกอย่างเรียบร้อยกด Create project ได้เลย

  • จากนั้นก็รอสักระยะ

  • เสร็จแล้ว!!! กด Continue ได้เลย!

  • เมื่อมาถึงหน้านี้หากท่านยังไม่เคยใช้งานมาก่อน ตั้งสติและใจเย็นๆ 😮‍💨😮‍💨 บทความนี้เราจะใช้แค่ Authentication เท่านั้น! 🧐

  • แทบเมนูด้านซ้ายเลือก Build และ **Authentication **ที่เราหมายปอง

  • เช่นเดิมหน้านี้ก็ใจเย็นๆ จุดหมายเราคือปุ่ม Get stated แต่ถ้าคุณอยากรู้อะไรเพิ่มเติมก็ดูคลิปที่เขานำมาให้รอก็ได้ ถ้าไม่เก่งอังกฤษ (เหมือนผม 😅) ก็เปิดคำบรรยายแล้วแปลอัตโนมัติเป็นไทย ก็ได้เช่นกัน ถ้าพร้อมแล้วค่อยมากด Get stated

  • เหล่านี้คือเหล่า providers ที่เราสามารถใช้ในการ **Authentication **ได้แต่สิ่งที่เราสนใจแบบง่ายๆพื้นๆคือ **Email/Password **ส่วนอื่นๆไม่ต้องไปสนใจ! เพราะผมยังใช้ไม่เป็น 😎เพราะงั้นเลือก **Email/Password **ได้เลย

  • ทำการ Enable ที่ **Email/Password **แล้วเลือก Saveได้เลย ส่วน Email link (passwordless sign-in) ยังไม่ได้ลองเช่นกันครับผม เพราะงั้นช่างมัน 😅

  • ถ้าได้แบบนี้เป็นการเสร็จในส่วนนี้แล้ว

ต่อไปเราจะไปเตรียมโฟลเดอร์และไฟล์

จัดการโฟลเดอร์และไฟล์ให้เตรียมพร้อมใช้งาน

  1. สร้างโฟลเดอร์

  • เพื่อไว้เก็บทุกอย่างที่เราจะทำ ส่วนนี้จะเป็นส่วนที่ยากที่สุดเพราะไม่รู้ว่าจะใช้ชื่อว่าอะไรดี 😅 จริงๆจะใช้ชื่อว่าอะไรก็ได้ แต่ทางผมของตั้งชื่อว่า **fastapi-firebase-auth **แล้วกันจากนั้นเข้าไปในโฟลเดอร์ให้เรียบร้อย
  1. เราจะทำการนำโฟลเดอร์ที่เราสร้างไปใช้งานใน **Visual Studio Code **หากคุณใช้ windows คุณสามารถทำตามนี้ได้เลย (หากคุณใช้อย่างอื่นคุณต้องค่อยๆ cd ไปยังโฟลเดอร์ของคุณและใช้ code . ได้เหมือนกัน)

  1. เนื่องจากว่าคุณจำเป็นต้องติดตั้ง firebase_admin และ fastapi ดังนั้นเราจึงต้องสร้าง virtual environment ขึ้นด้วยด้วยคำสั่ง
python -m venv .env
  • โดย .env คือชื่อจะใช้ชื่ออะไรก็ แต่แนะนำว่าใช้ชื่อนี้ง่ายกว่า

  • เมื่อทุกอย่างเรียบร้อยจะเป็นแบบนี้จากนั้นทำการเปิดใช้งานด้วยคำสั่ง
.env/Scripts/Activate.ps1
  • หากคุณใช้ Terminal คือ Power Shell (ตรงนี้หากไม่ได้ลองศึกษาเรื่อง virtual environment python เพิ่มเติมนะครับ)

  • แต่หากไม่ได้จริงๆ ก็ไม่เป็นไร สำหรับมือใหม่มันอาจจะยุ่งยาก ทำขั้นต่อไปกันได้เลย!

ติดตั้งสิ่งต่างที่ต้องใช้กัน

  1. ติดตั้ง firebase-admin กัน https://pypi.org/project/firebase-admin/
pip install firebase-admin

  1. ติดตั้ง FastAPI กัน https://pypi.org/project/fastapi/ เพื่อใช้เป็น web framework
pip install fastapi

  1. ติดตั้ง Uvicorn กัน https://pypi.org/project/uvicorn/ เพื่อใช้เป็นตัว run server
pip install uvicorn

ส่วนนี้เสร็จเรียบร้อยต่อไปเราจะกลับไปที่ firebase เพื่อเอาอะไรบางอย่างจากมัน 🧐

ไปเอา Certificate จาก Firebase

  1. จากที่หน้าที่เราทำกันล่าสุดที่ firebase ทำการที่รูป ⚙️ หรือรูปเฟือง และเลือก Project settings

  1. จากนั้นไปที่แท็ป Service Accounts

  1. เลือกเป็น Python ก่อนจากนั้นก็ Generate new private key

  1. เลือก generate key

  1. คุณจะได้ไฟล์ .json มาจากนั้นนำมันมาใส่ไว้ในโฟลเดอร์ที่เราเตรียมก่อนหน้านี้

  1. เปลี่ยนชื่อใหม่ให้มันเพื่อความใช้งานง่าย จริงจะใช้ชื่อว่าอะไรก็ได้ แต่ส่วนนี้แนะนำแบบ 100% ให้ใช้ชื่อ **service-account.json **แบบนี้ดีกว่า ระวังด้วยหากคุณจะเปลี่ยนชื่อที่โฟลเดอร์เลย ไม่ต้องใช้ .json แต่ถ้าคุณเปลี่ยนใน vscode ต้องใช้ .json ด้วย ไม่งั้นจะเป็นลักษณะนี้

แบบที่ถูกคือ ✅

ข้อมูลในนี้สำคัญหากคุณจะนำไปใช้ต่อระวังอย่าให้หลุด แต่ถ้าหลุดก็ไปลบแล้วเริ่มใหม่ 😅

เรียบร้อย เราจะไปส่วนต่อไปกัน

ลุยโค้ดกันได้เลย !

  1. สร้างไฟล์ app.py ขึ้นมา

  2. จากนั้นเราจะมาเริ่มใช้งาน FastAPI กันโดยคัดลอกโค้ดด้านล่างไปใส่ที่ app.py ได้เลย

app.py
from fastapi import FastAPI

app = FastAPI()


@app.get("/")
def index():
return {"message": "Hello World"}
  1. จากนั้น run server ด้วย uvicorn แล้วลอง test ด้วย Postman ดู
uvicorn app:app --reload
  • app แรกคือชื่อไฟล์ app.py และ app ต่อไปคือชื่อตัว app = FastAPI() และ reload คือ เมื่อคุณแก้ไขโค้ดและบันทึกมันจะรีโหลด web server ให้อัตโนมัติโดยไม่ต้องมาหยุดแล้วรันใหม่ โอเคนะ 😎

  1. สร้าง **init_firebase.py **เพื่อจะใช้เป็นเชื่อมต่อกับ firebase ด้วยไฟล์ service-account.json ที่เราได้มา โค้ดดังนี้
init_firebase.py
from firebase_admin import credentials, initialize_app

cred = credentials.Certificate("service-account.json")
initialize_app(cred)
print("firebase init done")
  1. กลับไปแก้ไขโค้ด app.py เพิ่มเติม
app.py
from init_firebase import *
from fastapi import FastAPI

app = FastAPI()


@app.get("/")
def index():
return {"message": "Hello World"}
  1. แล้วบันทึกรอรีโหลดหากมีข้อความ **firebase init done **ก็ถือเรียบร้อย 😎

  1. สร้างไฟล์ชื่อว่า models.pyเราจะทำการกำหนดรูปแบบ payload ที่เราต้องการนำเข้ามาซึ่งเราจะทำแบบง่ายๆจะรับเข้ามาแค่ email กับ password แบบนี้
models.py
from pydantic import BaseModel


class User(BaseModel):
email: str
password: str
  1. เราได้รูปแบบ payload แล้วเราจะกลับไปที่ app.py เพื่อทำการสร้างเส้น api ใหม่คือเส้น /signup เพื่อใช้ในการสมัครสมาชิกนั้นเองโดยจะใช้ method เพื่อจะรับ payload เข้ามา และเหมือนเดิมคัดลอกไปวางทับโค้ดเดิมใน app.py ได้เลยอย่าลืมบันทึุกด้วย 😎
app.py
from init_firebase import *
from fastapi import FastAPI
from models import User

app = FastAPI()


@app.get("/")
def index():
return {"message": "Hello World"}

@app.post("/signup")
def sign_up(user: User):
return user
  1. เทสใน Postman ได้เลยโดย payload เอาไปใส่ที่ตรง raw ได้เลยแล้วกด **Send **จะได้แบบรูปแบบ
payload
    {
"email":"wk23@gmail.com",
"password":"213sad2w2"
}

  1. ต่อเราจะทำการโค้ดในส่วนของการสมัครสมาชิกลงใน firebase กันจากโค้ดนี้ไปใส่ที่ app.py แล้วลองทดสอบเหมือนก่อนหน้าอีกครั้ง
app.py
from init_firebase import *
from fastapi import FastAPI
from models import User
from firebase_admin import auth

app = FastAPI()


@app.get("/")
def index():
return {"message": "Hello World"}


@app.post("/signup")
def sign_up(user: User):
email = user.email
password = user.password
user = auth.create_user(email=email, password=password)
print(user)
return user

11 . ลองกลับไปที่ firebase ส่วนของ Authentication คุณจะเห็นว่ามี email ตัวอย่างของเราโผล่ที่นั่นแล้ว!!

  1. แต่หากกลับไป Postman เราได้ข้อมูลอะไรมาเยอะแยะเลย เราจะมาดูส่วนที่สำคัญหน่อยแล้วกัน

payload
    {
"_data": {
"localId": "l6fNK2I2RwOft7FXV1ACpEEgX3e2",
"email": "wk23@gmail.com",
"passwordHash": "UkVEQUNURUQ=",
"emailVerified": false,
"passwordUpdatedAt": 1708104981299,
"providerUserInfo": [
{
"providerId": "password",
"federatedId": "wk23@gmail.com",
"email": "wk23@gmail.com",
"rawId": "wk23@gmail.com"
}
],
"validSince": "1708104981",
"disabled": false,
"createdAt": "1708104981299"
}
}
  • localId หรือ user_id เป็นรหัสสุ่มขึ้นมาไม่ซ้ำหรือใช้มันเป็น primary key ได้นั่นเอง

  • emailVerified เป็นสถานะที่บอกว่าอีเมลที่สมัครสมาชิกเข้ามานี้ทำการยืนยันอีเมลแล้วหรือยัง

  1. ต่อเราจะทำการสร้างเส้น /signin กันหรือเส้นที่จะใช้ในการเข้าสู่ระบบกัน แต่ว่าโชคร้ายที่ firebase_admin นี้ไม่มีการ sign in ด้วย email และ password 😭 แต่อย่าเศร้ามีวิธีแก้ 😎
  • ทำการติดตั้ง requests https://pypi.org/project/requests/ เพิ่มเติม อย่าลืมหยุด web server โดยไปที่ Termibal การกด Ctrl + C
pip install requests
pip install python-dotenv
  • กลับไปที่ firebase เลือก Project settings

  • คัดลอกข้อมูลที่อยู่หลัง Web API Key ไว้

  • สร้างไฟล์ _env เฉยๆ ไม่มีนามสกุลไฟล์นะ ไว้เก็บข้อมูลสำคัญอย่าง Web API Key จากนั้นวางลงแบบนี้ อย่าลืมเปลี่ยน key นะของใครของมันถ้าไม่เชื่อ error ไม่รู้น๊า
_env
FIREBASE_WEB_API_KEY=AIzaSyBQ8J_XYglnULI1U8vHnPYD-uz1TEjrxZY
  • สร้างไฟล์หน่อยแล้วกันเวลาเรียก FIREBASE_WEB_API_KEY จะได้เรียกง่ายๆชื่อว่า settings.py แล้วใส่โค้ดนี้ลงไปได้เลย
settings.py
import dotenv
import os

dotenv.load_dotenv("_env")

FIREBASE_WEB_API_KEY = os.environ.get("FIREBASE_WEB_API_KEY")
  • แล้วทำการสร้างไฟล์ rest_firebase_api.py เราจะทำการใช้ของที่ firebase_admin ของ python ที่ไม่ได้ใส่มาใช้กัน คัดลอกโค้ดไปได้เลย
rest_firebase_api.py
from settings import FIREBASE_WEB_API_KEY
import requests
import json

SIGN_IN_WITH_PASSWORD_URL = (
"https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword"
)


def sign_in_with_email_and_password(
email: str, password: str, return_secure_token: bool = True
):
payload = json.dumps(
{"email": email, "password": password, "returnSecureToken": return_secure_token}
)

r = requests.post(
SIGN_IN_WITH_PASSWORD_URL, params={"key": FIREBASE_WEB_API_KEY}, data=payload
)

return r.json()
  • ต่อกันยังไม่จบในส่วนนี้เราได้ทำการสร้างฟังก์ที่ไว้ใช้ sign in เรียบร้อยแล้ว ต่อเราจะไปเขียนโค้ดในส่วน app.py กันต่อเหมือนเช่นเดิม คัดลอกไปเลย
app.py
from init_firebase import *
from fastapi import FastAPI
from models import User
from firebase_admin import auth
from rest_firebase_api import sign_in_with_email_and_password

app = FastAPI()


@app.get("/")
def index():
return {"message": "Hello World"}


@app.post("/signup")
def sign_up(user: User):
email = user.email
password = user.password
user = auth.create_user(email=email, password=password)
return user


@app.post("/signin")
def sign_in(user: User):
email = user.email
password = user.password
user = sign_in_with_email_and_password(email, password)
return user
  1. บันทึกแล้วรันด้วยคำสั่งเดิมและเทสใน Postman ได้เลยถ้าขึ้นแบบข้างล่างแสดงว่าสำเร็จ หากไม่สำเร็จเช็ค email กับ password ใน payload ใหม่ดูน๊า

  1. ทีนี้ลองเทสกรณีรหัสผิดหรืออีเมลผิดดู
payload
    {
"email":"pg23@gmail.com",
"password":"213sad2w2"
}

กับ

payload
    {
"email":"wk23@gmail.com",
"password":"sdsddsd"
}

payload
    {
"email":"wk23@gmail.com",
"password":"213sad2w2"
}

  1. ถือว่าเยี่ยมเลยเราทำระบบ login เสร็จเรียบร้อยแล้วต่อไปเราจะให้มัน response ข้อมูล email_verified เข้ามาด้วยเพราะเราอยากรู้สถานะของผู้ใช้รายนี้ว่ายืนยันอีเมลหรือยังเราอาจจะเอาสถานะนี้ไปจำกัดสิทธิ์อะไรในอนาคตได้ เริ่มกันเหมือนเดิมคัดลอกไปได้เลย เราจะแก้ไขโค้ดในส่วน *def sign_in(user: User): *นี้เท่านั้นเพราะงั้นจะเอามาแค่ส่วนนี้จะได้ประหยัดพื้นที่นะ ที่ไฟล์ **app.py **เหมือนเช่นเคย
app.py
@app.post("/signin")
def sign_in(user: User):
email = user.email
password = user.password
user = sign_in_with_email_and_password(email, password)
email_verified = auth.get_user_by_email(email).email_verified
user["email_verified"] = email_verified
return user
  1. ลองเทสบน Postman ได้เลย ก็จะพบมาสถานะยังไม่ได้ยืนยันอีเมล

  1. และเส้นต่อไปแน่น่อนว่าต้องเป็นเส้น /send-email-verify โดยใช้เป็น method get หากมายิงเส้นส่งอีเมลไปหาอีเมลของผู้ใช้ที่มาการสมัครสมาชิกเข้ามา แต่ว่าการจะใช้เส้นนี้ได้จำเป็นต้องมี idToken สิ่งจะมาจาก เส้น /signin

โดยจะมี 2 ท่าให้เล่นคือ

    1. เส้น /send-email-verify ทำการรับ email และ password เข้ามาและเรียกฟังก์ชัน sign_in_with_email_and_password(email, password) เหมือน เส้น /signin วิธีนี้ไม่จำเป็นต้อง sign-in ก็ verify email ได้
    1. เส้น /signin หลังจากยิงเส้นนี้แล้วให้ทำการบันทึก idToken เป็น cookie เอาไว้ แล้วเส้น /send-email-verify ไม่จำเป็นต้องรับอะไรเข้าไป ไปดึง cookie idToken ที่บันทึกมาใช้ได้เลย วิธีนี้เลยจำเป็นต้อง sign-in ก่อนถึงจะ verify ได้

แน่นอนว่าเราเป็นคนคูลๆแบบสองมันดูยากกว่าแบบแรก เพราะงั้นเราเลือกวิธีที่ 2 😅 เพราะมันดูสมเหตุสมผลกว่าแค่นั้นเอง งั้นมาเริ่มโค้ดกันเหมือนเดิมเราจะไปแก้ที่ เส้น /signin โค้ดด้านล่างก็เอาไปใส่ที่ app.py

app.py
from datetime import datetime, timedelta
from init_firebase import *
from fastapi import FastAPI, Response
from models import User
from firebase_admin import auth
from rest_firebase_api import sign_in_with_email_and_password

app = FastAPI()


@app.get("/")
def index():
return {"message": "Hello World"}


@app.post("/signup")
def sign_up(user: User):
email = user.email
password = user.password
user = auth.create_user(email=email, password=password)
return user


@app.post("/signin")
def sign_in(user: User, response: Response):
email = user.email
password = user.password
user = sign_in_with_email_and_password(email, password)
email_verified = auth.get_user_by_email(email).email_verified
user["email_verified"] = email_verified
if "idToken" in user:
response.set_cookie(
key="id_token",
value=user["idToken"],
expires=int((datetime.utcnow() + timedelta(hours=1)).timestamp()),
)
return user

โดยจะดักด้วยว่าถ้าผู้ใช้งานกรอกอีเมลหรือรหัสผ่านไม่ถูกจะไม่ทำการ set_cookie

— key จะเปลี่ยนเป็นชื่ออื่นได้ — value คือข้อมูล idToken ที่อยู่ใน user — expires=datetime.utcnow() + datetime.timedelta(hours=1) cookie นี้จะถูกลบหรือหมดอายุไปในอีก 1 ชั่วโมง สามารถเปลี่ยนได้เช่น days=1 หรือ minutes=1 หรือ seconds=10 และแปลงเป็น timestamp อีกที

  • บันทึกแล้วทดสอบได้เลย แล้วไปที่แท็ป Cookies

  • ก่อนที่เราจะไปสร้างเส้น /send-email-verify กันต่อคงต้องหยุดก่อนสหาย เพราะว่าเสียใจด้วย firebase_admin ก็ไม่มี send-email-verify ให้เราใช้อีกแล้วครับท่าน 😭 ตั้งนั้นเราต้องใช้ท่าวรยุทธ์ rest เหมือนการ sign in เลย เพราะงั้นกลับไปที่ rest_firebase_api.py แล้วใส่โค้ดนี้ลงไปได้เลย
rest_firebase_api.py
from settings import FIREBASE_WEB_API_KEY
import requests
import json

SIGN_IN_WITH_PASSWORD_URL = (
"https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword"
)

SEND_VERIFY_EMAIL_URL = "https://identitytoolkit.googleapis.com/v1/accounts:sendOobCode"


def sign_in_with_email_and_password(
email: str, password: str, return_secure_token: bool = True
):
payload = json.dumps(
{"email": email, "password": password, "returnSecureToken": return_secure_token}
)
r = requests.post(
SIGN_IN_WITH_PASSWORD_URL, params={"key": FIREBASE_WEB_API_KEY}, data=payload
)

return r.json()


def send_verify_email(id_token):
headers = {"Content-Type": "application/json"}
url = f"{SEND_VERIFY_EMAIL_URL}?key={FIREBASE_WEB_API_KEY}"
data = {"requestType": "VERIFY_EMAIL", "idToken": id_token}
response = requests.post(
url, headers=headers, json=data, auth=("api_key", FIREBASE_WEB_API_KEY)
)
return response
  • เรียบร้อยพร้อมใช้งานที่นี้ไปสร้างเส้น /send-email-verify ที่ **app.py **ได้แล้ว 😎 ลุย
app.py
from datetime import datetime, timedelta
from init_firebase import *
from fastapi import FastAPI, Response, Request
from models import User
from firebase_admin import auth
from rest_firebase_api import send_verify_email, sign_in_with_email_and_password

app = FastAPI()


@app.get("/")
def index():
return {"message": "Hello World"}


@app.post("/signup")
def sign_up(user: User):
email = user.email
password = user.password
user = auth.create_user(email=email, password=password)
return user


@app.post("/signin")
def sign_in(user: User, response: Response):
email = user.email
password = user.password
user = sign_in_with_email_and_password(email, password)
email_verified = auth.get_user_by_email(email).email_verified
user["email_verified"] = email_verified
if "idToken" in user:
response.set_cookie(
key="id_token",
value=user["idToken"],
expires=int((datetime.utcnow() + timedelta(hours=1)).timestamp()),
)
return user


@app.get("/send-email-verify")
def send_email_verify(request: Request):
id_token = request.cookies.get("id_token")
if id_token:
send_verify_email(id_token)
else:
return {"message": "please login first 😭"}
return {"message": "send email verify 😎"}
  • มาลองเทสกับ อันดับแรกอาจจะต้องใช้ อีเมลที่เราสามารถรับส่งที่ firebase จะส่งมาให้เราได้ หรืออาจจะใช้ Temp Mail ทดสอบก็ได้

  • ขั้นแรกต้องเริ่มตั้งแต่ต้น คือ signup นั่นเอง เริ่มกัน ผมจะใช้ Temp Mail อันดับแรกไปเอามาก่อนที่ Temp Mail คัดลอกอีเมลมาโดยอีเมลมันจะสุ่มนะบอกไว้ก่อนอย่าใช้ตามผมเชื่อผม 😎 ใช้ของที่คุณได้ดีกว่า

  • และเตรียม payload ไว้
payload
    {
"email":"cahinab325@fkcod.com",
"password":"12345678"
}
  • เอาไปใส่แล้วยิงเส้น /signup ได้เลย

  • และเราต้อง sign in ก่อนถึงจะใช้ เส้น /send-email-verify ได้เพราะไม่งั้น อาจจะเกิดอาการเศร้าได้นะ 😭 เช่นแบบนี้ เพราะผมดักไว้แล้ว 😁

  • โอเคเอาจริงล่ะ เราต้อง sign in ก่อน เริ่มได้ payload เดิมเลย ยิงที่เส้น /signin กันลุย
payload
    {
"email":"cahinab325@fkcod.com",
"password":"12345678"
}

  • แล้วต่อพระเอกของเราคือเส้น /send-email-verify 🥳 มาลองดูกัน อย่าลืมเปลี่ยน method เป็น GET นะ

  • ต่อไปก็เช็คที่ Temp Email ที่เราได้มาได้เลยว่ามีอะไรส่งมาไหม?

  • ทำการยืนยันอีเมลโดยคลิกลิงก์ได้เลย

  • ก็จะได้หน้าตาแบบนี้นั่นเอง

  • กลับตรวจสอบที่เส้น /signin อีกรอบว่า email_verified เปลี่ยน true หรือยัง ถ้าเรียบร้อยแสดงว่าใช้งานได้

  1. แน่นอนว่าข้อความที่ส่งไปในอีเมลของผู้ใช้นั้นดูไม่สวยเลย คุณสามารถแต่งได้นะโดยไปที่ firebase ที่ Authentication แล้วเลือกแท็บ Templates

  1. แก้ไขข้อความบางส่วนได้ไม่สามารถแก้ Message ได้เพราะ firebase บอกว่าเพื่อเป็นการป้องกันสแปม

  1. แต่คุณสามารถเปลี่ยนภาษาได้นะ โดยไปที่ Template language

คร่าวๆ ก็จะประมาณนี้ แต่ถ้าอยากไปต่อหล่ะก็ลิงก์ที่มันยาวๆ ตอนยืนยันอีเมล์ เราสามารถจัดการให้มันยิงตรงมายัง web server เราได้ 😎 เป็นยังไปดูส่วนถัดไปเลย

ปรับแต่ง URL ยืนยันอีเมล

  1. เราจะต้องทำการเขียนโค้ดกันต่อโดยที่ **rest_firebase_api.py **เราจะทำการเปลี่ยน url จาก https://fir-fastapi-33292.firebaseapp.com เป็นเส้น api ที่สร้างขึ้นเอง แต่ก่อนจะไปจุดนั้นต้องสร้างฟังก์ชันที่จะใช้ในการรองรับมันก่อนโดยการทำงานเดิมนั้น firebase จะส่งออกมาสองข้อมูล คือ mode=action&oobCode=code หรือก็คือ mode=verifyEmail&oobCode=IpA6RRNFU….. บลาๆ แต่สิ่งที่เราสนใจคือ oobCode เพราะงั้นเราจะทำการดึงข้อมูล oobCode ออกมาจาก url บ้านั่นกันและเอามาใช้กันในฟังก์ชันที่จะสร้างขึ้นชื่อว่า verify_email แน่นอนเตรียมลุยมาดูกันจะออกมาท่าวรยุทธ์ไหน
rest_firebase_api.py
from settings import FIREBASE_WEB_API_KEY
import requests
import json

SIGN_IN_WITH_PASSWORD_URL = (
"https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword"
)

SEND_VERIFY_EMAIL_URL = "https://identitytoolkit.googleapis.com/v1/accounts:sendOobCode"

VERIFY_EMAIL_URL = (
"https://www.googleapis.com/identitytoolkit/v3/relyingparty/setAccountInfo"
)


def sign_in_with_email_and_password(
email: str, password: str, return_secure_token: bool = True
):
payload = json.dumps(
{"email": email, "password": password, "returnSecureToken": return_secure_token}
)
r = requests.post(
SIGN_IN_WITH_PASSWORD_URL, params={"key": FIREBASE_WEB_API_KEY}, data=payload
)

return r.json()


def send_verify_email(id_token):
headers = {"Content-Type": "application/json"}
url = f"{SEND_VERIFY_EMAIL_URL}?key={FIREBASE_WEB_API_KEY}"
data = {"requestType": "VERIFY_EMAIL", "idToken": id_token}
response = requests.post(
url, headers=headers, json=data, auth=("api_key", FIREBASE_WEB_API_KEY)
)
return response


@app.get("/verify-email")
async def verify_email_link(request: Request):
oob_code = request.query_params.get("oobCode")
print(oob_code)
if oob_code:
response = verify_email(oob_code).json()
if "error" in response:
return {"message": response["error"]["message"]}
else:
return {"message": "please click link at email 😭"}
return {"message": "verify email 😎"}
  1. มาสร้างเส้นที่จะให้ผู้ใช้งานเข้ามายืนยันกัน ขอใช้ชื่อว่า /verify-email แล้วกันง่ายดี มาลุยโค้ด จะมีการปรับขนาดใหญ่ เพราะผมลืม async 😅 จริงๆควรใส่ทุก ฟังก์ชันเลย เพราะว่าลืมแต่ครั้งนี้ใส่ให้ครบแล้ว กลับที่ **app.py **แล้วลุยเลย
app.py
from datetime import datetime, timedelta
from init_firebase import *
from fastapi import FastAPI, Response, Request
from starlette.requests import Request as StarletteRequest
from models import User
from firebase_admin import auth
from rest_firebase_api import (
send_verify_email,
sign_in_with_email_and_password,
verify_email,
)

app = FastAPI()


@app.get("/")
async def index():
return {"message": "Hello World"}


@app.post("/signup")
async def sign_up(user: User):
email = user.email
password = user.password
user = auth.create_user(email=email, password=password)
return user


@app.post("/signin")
async def sign_in(user: User, response: Response):
email = user.email
password = user.password
user = sign_in_with_email_and_password(email, password)
email_verified = auth.get_user_by_email(email).email_verified
user["email_verified"] = email_verified
if "idToken" in user:
response.set_cookie(
key="id_token",
value=user["idToken"],
expires=int((datetime.utcnow() + timedelta(hours=1)).timestamp()),
)
return user


@app.get("/send-email-verify")
async def send_email_verify(request: Request):
id_token = request.cookies.get("id_token")
if id_token:
send_verify_email(id_token)
else:
return {"message": "please login first 😭"}
return {"message": "send email verify 😎"}


@app.get("/verify-email")
async def verify_email_link(request: Request):
oob_code = request.query_params.get("oobCode")
print(oob_code)
if oob_code:
response = verify_email(oob_code).json()
if "error" in response:
return {"message": response["error"]["message"]}
else:
return {"message": "please click link at email 😭"}
return {"message": "verify email 😎"}
  1. เนื่องจากว่าเราเคยสมัครบัญชีนี้แล้ว ถ้าไม่อยากเปลืองอีเมลก็สามารถไปลบได้ที่ firebase เลยเหมือนเดิมแล้วก็ไปที่ Users แล้วเลือกผู้ใช้ที่จะลบ ถ้ายังไม่ขึ้นลองกดปุ่ม reset ข้างปุ่ม Add user ทางขวา

  1. ทำการเปลี่ยน url ที่จะถูกส่งไปยังอีเมลก่อน ไปที่ Templates แล้วเลือกรูปดินสอหรือแก้ไข

  1. เลือก Customize action URL แล้วก็ใส่ http://localhost:8000/verify-email หรือ http://127.0.0.1:8000/verify-email แล้วก็ Save เลย

  1. แล้วก็เทสเหมือนเดิมตั้งแต่แรกเลย ขอส่งเป็นรูปยาวๆนะจ๊ะ 😁
payload
    {
"email":"toxis52325@fkcod.com",
"password":"12345678"
}

เส้น /signup

เส้น /signin

เส้น /send-email-verify

หน้าอีเมล

หลังคลิกลิงก์

เส้น /signin ก่อน verify email

เส้น /signin หลัง verify email

ยอดเยี่ยมไปเลยใช่มั้ยการ Authentication ด้วย Firebase จริงๆมีอะไรที่อยากทำอีกเช่น Reset password , เปลี่ยนอีเมล , SMS verification และเชื่อมต่อฐานข้อมูลอย่าง MongoDB พวกนี้ แต่บทความนี้ยาวล่ะ ไม่รู้ว่าจะมีคนมาอ่านหรือเปล่า 😅 แต่ก็นั่นแหละใครที่มาอ่านถือว่าเรามีวาสนาต่อกันนะครับ ท้ายนี้ก็ขอบคุณที่อ่านบทความยันจบนะครับ เพราะว่าตอนแรกการใช้ Firebase สำหรับผมเป็นอะไรที่คิดว่ายากมากๆเลย แต่พอได้เริ่มเขียนและลองศึกษาอย่างจริงจังล่ะ เขาเตรียทุกอย่างไว้ให้เยอะมากนะครับ ใครอยากดูท่าวรยุทธ์ต่างๆเพิ่มเติมไปดูได้ที่ https://github.com/firebase/firebase-admin-python/blob/master/integration/test_auth.py เขาเขียนไว้ให้แล้วเราแค่ดึงมาใช้ หวังว่าจะได้เจอกันอีกบทความหน้านะครับถ้าผม ขยัน 😁 ใครติดโค้ดตรงไหนรันไม่ได้ไปดูได้ที่นี่ครับ https://github.com/watchakorn-18k/fastapi-firebase-auth